/**
 * 这是一个用于存储表单的工具函数。它通过localStorage和BroadcastChannel实现数据的保存、加载、同步和清除功能。
 * 
 * @class FormStorage
 * @param {String} formId - 表单的唯一标识符，用于区分不同的表单。
 * @param {Object} formData - 需要存储的表单数据。
 */
class FormStorage {
  constructor(formId, formData) {
    // 本地存储的键名
    this.formId = formId;
    this.storageKey = `formData_${formId}`;
    this.formData = formData;
    this.channel = new BroadcastChannel("form_sync");
    this.debouncedSaveData = this.debounce(this.saveData, 3000).bind(this);
    FormStorageManager.register(this); // 注册实例到管理类

    // 监听 BroadcastChannel 消息
    this.channel.onmessage = (event) => {
      const { formId, data } = JSON.parse(event.data);
      if (formId === this.formId) {
        this.syncData(data);
      } else if (formId === "all") {
        this.clearData();
      }
    };
  }

  /**
  * 初始化函数，用于加载本地存储的数据。
  * 
  * @method init
  */
  init() {
    this.loadData();
  }

  /**
  * 保存数据到本地存储，并通过广播通道发送消息。
  * 
  * @method saveData
  */
  saveData() {
    // this.formData.value需要.value，是由于钩子函数拼了个value为键的对象
    const data = JSON.stringify(this.formData);
    localStorage.setItem(this.storageKey, data);
    this.channel.postMessage(
      JSON.stringify({ formId: this.formId, data: this.formData })
    );
  }

  /**
  * 防抖函数，用于限制函数的执行频率。
  * 
  * @method debounce
  * @param {Function} func - 需要防抖的函数。
  * @param {Number} wait - 等待的时间，单位为毫秒。
  * @return {Function} 返回一个防抖后的函数。
  */
  debounce(func, wait) {
    let timeout;
    return function(...args) {
      clearTimeout(timeout);
      timeout = setTimeout(() => func.apply(this, args), wait);
    };
  }

  /**
  * 从本地存储加载数据。
  * 
  * @method loadData
  */
  loadData() {
    const savedData = JSON.parse(localStorage.getItem(this.storageKey));
    if (savedData) {
      for (const key in savedData) {
        // this.formData.value需要.value，是由于钩子函数拼了个value为键的对象
        if (Object.prototype.hasOwnProperty.call(this.formData, key)) {
          this.formData[key] = savedData[key];
        }
      }
    }
  }

  /**
  * 清除本地存储的数据和广播通道的消息。
  * 
  * @method clearData
  */
  clearData() {
    localStorage.removeItem(this.storageKey);
    this.channel.postMessage(
      JSON.stringify({ formId: this.formId, data: null })
    );
  }

  /**
  * 根据接收到的数据同步表单数据。
  * 
  * @method syncData
  * @param {Object} data - 需要同步的数据。
  */
  syncData(data) {
    if (data) {
      for (const key in data) {
        // this.formData.value需要.value，是由于钩子函数拼了个value为键的对象
        if (Object.prototype.hasOwnProperty.call(this.formData, key)) {
          this.formData[key] = data[key];
        }
      }
    }
  }

  /**
  * 清除所有表单的数据。
  * 
  * @static
  * @method clearAll
  */
  static clearAll() {
    const keysToRemove = Object.keys(localStorage).filter((key) =>
      key.startsWith("formData_")
    );
    keysToRemove.forEach((key) => localStorage.removeItem(key));
    const channel = new BroadcastChannel("form_sync");
    channel.postMessage(JSON.stringify({ formId: "all", data: null }));
  }
}

/**
 * 主要用于控制FormStorage类的实例。
 *
 * @class FormStorageManager
 */
// 主要用于控制 FormStorage 类的实例
class FormStorageManager {
  static instances = [];
  // 判断是否是第一次初始化
  static isInitialized = false;

  /**
   * 注册FormStorage的实例，并在第一次注册时初始化监听事件。
   * 
   * @static
   * @method register
   * @param {FormStorage} instance - 需要注册的FormStorage实例。
   */
  static register(instance) {
    FormStorageManager.instances.push(instance);
    if (!FormStorageManager.isInitialized) {
      // 只有在第一次注册实例时才会初始化监听事件
      window.addEventListener("beforeunload", FormStorageManager.saveAll);
      document.addEventListener(
        "visibilitychange",
        FormStorageManager.handleVisibilityChange
      );
      FormStorageManager.isInitialized = true;
    }
  }

  /**
   * 保存所有FormStorage实例的数据。遍历所有实例并调用它们的 saveData 方法。
   * 
   * @static
   * @method saveAll
   */
  static saveAll() {
    FormStorageManager.instances.forEach((instance) => instance.saveData());
  }

  /**
   * 处理页面可见性变化的事件，当页面不可见时保存所有数据。
   * 
   * @static
   * @method handleVisibilityChange
   */
  static handleVisibilityChange() {
    if (document.visibilityState === "hidden") {
      FormStorageManager.saveAll();
    }
  }
}

export default FormStorage;
